/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sentry;

import com.jakewharton.nopen.annotation.Open;

import io.sentry.protocol.SentryId;
import io.sentry.util.CollectionUtils;
import io.sentry.util.Objects;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

/**
 * SpanContext
 *
 * @since 2021-06-02
 **/
@Open
public class SpanContext implements Cloneable {
    /**
     * TYPE
     */
    public static final String TYPE = "trace";

    /**
     * Short code identifying the type of operation the span is measuring.
     */
    protected @NotNull String op;

    /**
     * Longer description of the span's operation, which uniquely identifies the span but is
     * consistent across instances of the span.
     */
    protected @Nullable String description;

    /**
     * Describes the status of the Transaction.
     */
    protected @Nullable SpanStatus status;

    /**
     * A map or list of tags for this event. Each tag must be less than 200 characters.
     */
    protected @NotNull Map<String, @NotNull String> tags = new ConcurrentHashMap<>();

    /**
     * Determines which trace the Span belongs to.
     */
    private final @NotNull SentryId traceId;

    /**
     * Span id.
     */
    private final @NotNull SpanId spanId;

    /**
     * Id of a parent span.
     */
    private final @Nullable SpanId parentSpanId;

    /**
     * If trace is sampled.
     */
    private transient @Nullable Boolean sampled;

    /**
     * constructor
     *
     * @param operation operation
     * @param sampled Boolean
     */
    public SpanContext(final @NotNull String operation, final @Nullable Boolean sampled) {
        this(new SentryId(), new SpanId(), operation, null, sampled);
    }

    /**
     * Creates trace context with defered sampling decision.
     *
     * @param operation the operation
     */
    public SpanContext(final @NotNull String operation) {
        this(new SentryId(), new SpanId(), operation, null, null);
    }

    /**
     * constructor
     *
     * @param traceId SentryId
     * @param spanId SpanId
     * @param operation String
     * @param parentSpanId parentSpanId
     * @param sampled Boolean
     */
    public SpanContext(
        final @NotNull SentryId traceId,
        final @NotNull SpanId spanId,
        final @NotNull String operation,
        final @Nullable SpanId parentSpanId,
        final @Nullable Boolean sampled) {
        this.traceId = Objects.requireNonNull(traceId, "traceId is required");
        this.spanId = Objects.requireNonNull(spanId, "spanId is required");
        this.op = Objects.requireNonNull(operation, "operation is required");
        this.parentSpanId = parentSpanId;
        this.sampled = sampled;
    }

    /**
     * setOperation
     *
     * @param operation operation
     */
    public void setOperation(final @NotNull String operation) {
        this.op = Objects.requireNonNull(operation, "operation is required");
    }

    /**
     * set tag
     *
     * @param name String
     * @param value String
     */
    public void setTag(final @NotNull String name, final @NotNull String value) {
        Objects.requireNonNull(name, "name is required");
        Objects.requireNonNull(value, "value is required");
        this.tags.put(name, value);
    }

    public void setDescription(final @Nullable String description) {
        this.description = description;
    }

    public void setStatus(final @Nullable SpanStatus status) {
        this.status = status;
    }

    @NotNull
    public SentryId getTraceId() {
        return traceId;
    }

    @NotNull
    public SpanId getSpanId() {
        return spanId;
    }

    @Nullable
    @TestOnly
    public SpanId getParentSpanId() {
        return parentSpanId;
    }

    /**
     * getOperation
     *
     * @return String op
     */
    public @NotNull String getOperation() {
        return op;
    }

    public @Nullable String getDescription() {
        return description;
    }

    public @Nullable SpanStatus getStatus() {
        return status;
    }

    public @NotNull Map<String, String> getTags() {
        return tags;
    }

    public @Nullable Boolean getSampled() {
        return sampled;
    }

    void setSampled(final @Nullable Boolean sampled) {
        this.sampled = sampled;
    }

    @Override
    public SpanContext clone() throws CloneNotSupportedException {
        final SpanContext clone = (SpanContext) super.clone();
        final Map<String, String> copiedTags = CollectionUtils.newConcurrentHashMap(this.tags);
        if (copiedTags != null) {
            clone.tags = copiedTags;
        }
        return clone;
    }
}
